package net.easyjava.third.search.elastic;

import com.alibaba.fastjson.JSONObject;
import net.easyjava.third.search.elastic.bean.RangeBean;
import net.easyjava.third.search.exception.SearchException;
import net.easyjava.third.search.utils.SearchUtils;
import net.easyjava.tools.lang.ArraysTools;
import net.easyjava.tools.lang.StringTools;
import net.easyjava.tools.page.Page;
import org.apache.lucene.queryparser.xml.FilterBuilder;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.*;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.highlight.HighlightBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.text.Highlighter;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

/**
 * Created by wxnacy on 16/7/6.
 */
public class ESearchClient {
    private static Logger logger = LoggerFactory.getLogger(ESearchClient.class);
    private  static TransportClient client = null;
    private static String clusterName;
    private static String ips;
    private static String ports;

    public TransportClient getClient(){
        if(client == null){
            throw new ESearchException("must do getInstance");
        }
        return client;
    }
    /**
     * 构造单例
     */
    private volatile static ESearchClient singleton;
    private ESearchClient(final String clusterName,final String ips,final String ports) throws UnknownHostException {
        this.clusterName = clusterName;
        this.ips = ips;
        this.ports = ports;
        if(client == null){
            init();
        }
    }
    public static ESearchClient getInstance(final String clusterName,final String ips,final String ports) throws UnknownHostException {
        if (singleton == null) {
            synchronized (ESearchClient.class) {
                if (singleton == null) {
                    singleton = new ESearchClient(clusterName,ips,ports);
                }
            }
        }
        if(client == null){
            init();
        }
        return singleton;
    }

    private static synchronized void init() throws UnknownHostException {
        long begin = System.currentTimeMillis();
        Settings settings = Settings.settingsBuilder()
                .put("cluster.name", clusterName)
                .build();
        TransportClient transportClient = TransportClient.builder().settings(settings).build();
        String[] ipArray = ips.split(StringTools.SEPARATE_DOUHAO);
        for(int i=0;i<ipArray.length;i++){
            String ip = ipArray[i];
            int port = Integer.valueOf(StringTools.unSeparate(ports, i, StringTools.SEPARATE_DOUHAO));
            if(i +1 == ipArray.length){
                client = transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ip), port));
            }else{
                transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ip), port));
            }
        }
        long end = System.currentTimeMillis();
        if(client == null){
            throw new ESearchException("client can't init");
        }
        logger.debug("ESearchClient.init cluster.name:{} ip:{} port:{} time:{}", clusterName, ips, ports, (end - begin));
        String result = String.format("ESearchClient.init cluster.name:%s ip:%s port:%s time:%s",clusterName,ips,ports,(end-begin));
        System.out.println(result);
    }

    public static void closeClient(){
        System.out.println("ESClient.closeClient");
        if(client != null){
            client.close();
            client = null;
        }
    }



    /**
     * 创建索引
     * @param object 需要使用Index和IndexID注解
     * @return
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public IndexResponse createIndex(Object object) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        if(object == null){
            throw new NullPointerException("object can't be null");
        }
        String index = SearchUtils.getIndex(object);
        String type = SearchUtils.getType(object);
        String id = SearchUtils.getId(object);

        String data = JSONObject.toJSONString(object);

        return createIndex(index,type,id,data);
    }

    /**
     * 根据索引和类型以及bean来创建索引
     * @param indexName
     * @param type
     * @param object
     * @return
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public  IndexResponse createIndex(String indexName,String type,Object object) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        String id = SearchUtils.getId(object);
        String source = JSONObject.toJSONString(object);
        return createIndex(indexName, type,id,source);
    }


    /**
     * 创建索引
     * @param indexName
     * @param type
     * @param id
     * @param sourceJson
     * @return
     */
    public  IndexResponse createIndex(String indexName,String type,String id,String sourceJson){
        if(id != null && "".equalsIgnoreCase(id)){
            throw new ESearchException("id can't be ''");
        }

        sourceJson = SearchUtils.putPinyinIn(sourceJson).toJSONString();
        IndexResponse response = client.prepareIndex(indexName, type, id)
                .setSource(sourceJson)
                .get();
        return response;
    }

    /**
     * 使用list bean批量创建索引
     * @param list
     * @param <T>
     * @return
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public <T> BulkResponse bulkCreateIndex(List<T> list) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        if(list == null || list.size() == 0){
            throw new NullPointerException("list can't be null or empty");
        }
        BulkRequestBuilder bulkRequest = client.prepareBulk();
        for(T ts:list){
            String id = null;
            String source = null;
            String indexName = null;
            String type = null;
            if(ts instanceof ESearchBean){
                ESearchBean bean = (ESearchBean) ts;
                id = bean.getId();
                indexName = bean.getIndex();
                type = bean.getType();
                source = bean.getSource();
            }else{
                id = SearchUtils.getId(ts);
                source = JSONObject.toJSONString(ts);
                indexName = SearchUtils.getIndex(ts);
                type = SearchUtils.getType(ts);
            }
            source = SearchUtils.putPinyinIn(source).toJSONString();
            bulkRequest.add(client.prepareIndex(indexName,type,id).setSource(source));
        }
        return bulkRequest.get();
    }

    public BulkResponse bulkCreateIndex(String indexName,String type,Object... sources) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        List list = Arrays.asList(sources);
        return bulkCreateIndex(indexName, type, list);
    }
    /**
     * 批量建立索引
     * @param indexName
     * @param type
     * @param objects
     * @return
     */
    public <T> BulkResponse bulkCreateIndex(String indexName,String type,List<T> objects) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        BulkRequestBuilder bulkRequest = client.prepareBulk();
        for(T t:objects){
            String id = null;
            String source = null;
            if(t instanceof String){
                source = SearchUtils.putPinyinIn((String)t).toJSONString();
            }else{
                id = SearchUtils.getId(t);
                source = SearchUtils.putPinyinIn(t).toJSONString();
            }
            bulkRequest.add(client.prepareIndex(indexName,type,id).setSource(source));
        }
        return bulkRequest.get();
    }

    /**
     * 批量删除
     * @param indexName
     * @param type
     * @param ids
     * @return
     */
    public BulkResponse bulkDeleteIndex(String indexName,String type,String... ids){
        if(ids == null){
            throw new NullPointerException("ids can't be null");
        }
        return bulkDeleteIndex(indexName, type, Arrays.asList(ids));
    }
    public BulkResponse bulkDeleteIndex(String indexName,String type,List<String> ids){
        if(ids == null){
            throw new NullPointerException("ids can't be null");
        }
        BulkRequestBuilder builder = client.prepareBulk();
        for(String id:ids){
            builder.add(client.prepareDelete(indexName,type,id));
        }
        return builder.get();
    }

    /**
     * 批量获取
     * @param indexName
     * @param type
     * @param ids
     * @return
     */
    public MultiGetResponse bulkGetIndex(String indexName, String type, String... ids){
        return client.prepareMultiGet().add(indexName, type, ids).get();
    }
    public <T> List<T> bulkGetIndex(String indexName, String type,Class<T> clazz, String... ids){
        List<T> list = new ArrayList<>();
        MultiGetResponse response = client.prepareMultiGet().add(indexName, type, ids).get();
        for(MultiGetItemResponse getItemResponse:response.getResponses()){
            String source = getItemResponse.getResponse().getSourceAsString();
            if(!StringTools.isEmpty(source)){
                list.add(JSONObject.parseObject(source,clazz));
            }
        }

        return list;
    }

    public MultiGetResponse bulkGetIndex(String indexName, String type, List<String> ids){
        return client.prepareMultiGet().add(indexName, type, ids).get();
    }
    public <T> List<T> bulkGetIndex(String indexName, String type, List<String> ids,Class<T> clazz){
        List<T> list = new ArrayList<>();
        MultiGetResponse response = client.prepareMultiGet().add(indexName, type, ids).get();
        for(MultiGetItemResponse getItemResponse:response.getResponses()){
            String source = getItemResponse.getResponse().getSourceAsString();
            if(!StringTools.isEmpty(source)){
                list.add(JSONObject.parseObject(source,clazz));
            }
        }
        return list;
    }








    /**
     * 读取索引
     * @param indexName
     * @param type
     * @param id
     * @return
     */
    public GetResponse getIndex(String indexName,String type,String id){
        GetResponse response = client.prepareGet(indexName, type, id).get();
        return response;
    }
    public <T> T getIndex(String id,Class<T> clazz){

        String indexName = SearchUtils.getIndex(clazz);
        String type = SearchUtils.getType(clazz);
        GetResponse response = getIndex(indexName, type, id);
        if(response.isExists() && !response.isSourceEmpty()){
            return JSONObject.parseObject(response.getSourceAsString(),clazz);
        }
        return null;
    }

    /**
     * 删除索引
     * @param indexName
     * @param type
     * @param id
     * @return
     */
    public  DeleteResponse deleteIndex(String indexName,String type,String id){
        DeleteResponse deleteResponse = client.prepareDelete(indexName, type, id).get();
        return deleteResponse;
    }

    /**
     *
     * @param indexName
     * @param type
     * @param queryString 搜索的关键字
     * @param pageNum
     * @param pageSize
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Page<T> search(String indexName,String type ,String queryString,int pageNum,int pageSize,Class<T> clazz) {
        String[] types = {type};
        return search(indexName, types, queryString, pageNum, pageSize, clazz);
    }
    public <T> Page<T> search(String indexName,String[] type ,String queryString,int pageNum,int pageSize,Class<T> clazz) {
        SearchResponse searchResponse = search(indexName, type, queryString, pageNum, pageSize);
        List<T> list = SearchUtils.getListFromResponse(searchResponse, indexName, clazz, queryString,true);
        return new Page<>(list,searchResponse.getHits().totalHits(),pageNum,pageSize);
    }
    public <T> Page<T> search(String indexName,String type ,String[] fields,String queryString,int pageNum,int pageSize,Class<T> clazz) {
        String[] types = {type};
        return search(indexName, types, fields, queryString, pageNum, pageSize, clazz);
    }
    public <T> Page<T> search(String indexName,String type ,String field,String queryString,int pageNum,int pageSize,Class<T> clazz) {
        String[] fields = {field};
        String[] types = {type};
        return search(indexName, types, fields, queryString, pageNum, pageSize, clazz);
    }
    public <T> Page<T> search(String indexName,String[] type ,String field,String queryString,int pageNum,int pageSize,Class<T> clazz) {
        String[] fields = {field};
        return search(indexName, type, fields, queryString, pageNum, pageSize, clazz);
    }
    public <T> Page<T> search(String indexName,String[] type ,String[] fields,String queryString,int pageNum,int pageSize,Class<T> clazz) {
        SearchResponse searchResponse = search(indexName, type,fields, queryString, pageNum, pageSize);
        List<T> list = SearchUtils.getListFromResponse(searchResponse, indexName, clazz, queryString,true);
        return new Page<>(list,searchResponse.getHits().totalHits(),pageNum,pageSize);
    }
    public <T> List<T> search(String indexName,String type ,String queryString,Class<T> clazz) {
        String[] types = {type};
        return search(indexName, types, queryString, clazz);
    }
    public <T> List<T> search(String indexName,String[] type ,String queryString,Class<T> clazz) {
        SearchResponse searchResponse = search(indexName, type, queryString, 0, 0);
        List<T> list = SearchUtils.getListFromResponse(searchResponse, indexName, clazz, queryString,true);
        return list;
    }
    public <T> List<T> search(String indexName,String type ,String[] fields,String queryString,Class<T> clazz) {
        String[] types = {type};
        return search(indexName, types, fields, queryString, clazz);
    }
    public <T> List<T> search(String indexName,String type ,String field,String queryString,Class<T> clazz) {
        String[] fields = {field};
        String[] types = {type};
        return search(indexName, types, fields, queryString, clazz);
    }
    public <T> List<T> search(String indexName,String[] type ,String field,String queryString,Class<T> clazz) {
        String[] fields = {field};
        return search(indexName, type, fields, queryString, clazz);
    }
    public <T> List<T> search(String indexName,String[] type ,String[] fields,String queryString,Class<T> clazz) {
        SearchResponse searchResponse = search(indexName, type,fields, queryString, 0, 0);
        List<T> list = SearchUtils.getListFromResponse(searchResponse, indexName, clazz, queryString,true);
        return list;
    }
    public <T> List<T> search(String indexName,String[] type ,String[] fields,String queryString,Class<T> clazz,boolean needHighLighter) {
        SearchResponse searchResponse = search(indexName, type,fields, queryString, 0, 0);
        List<T> list = SearchUtils.getListFromResponse(searchResponse, indexName, clazz, queryString,needHighLighter);
        return list;
    }

    public SearchResponse search(String indexName,String type ,String queryString,int pageNum,int pageSize) {
        String[] types = {type};
        return search(indexName, types, queryString, pageNum, pageSize);
    }
    public SearchResponse search(String indexName,String[] type ,String queryString,int pageNum,int pageSize) {
        ESearchConfig eSearchConfig = new ESearchConfig(indexName,type);
        eSearchConfig.setSearchType(ESearchConfig.SearchType.QUERY_ALL);
        eSearchConfig.setQuery(queryString);
        eSearchConfig.setPageNum(pageNum);
        eSearchConfig.setPageSize(pageSize);

        return search(eSearchConfig);
    }


    public SearchResponse search(String indexName,String type ,String queryField,String queryString,int pageNum,int pageSize) {
        String[] queryFields = {queryField};
        String[] types = {type};
        return search( indexName,types ,queryFields, queryString, pageNum, pageSize);
    }
    public SearchResponse search(String indexName,String type ,String[] queryField,String queryString,int pageNum,int pageSize) {
        String[] types = {type};
        return search( indexName,types ,queryField, queryString, pageNum, pageSize);
    }
    public SearchResponse search(String indexName,String[] type ,String queryField,String queryString,int pageNum,int pageSize) {
        String[] queryFields = {queryField};
        return search( indexName,type ,queryFields, queryString, pageNum, pageSize);
    }
    public SearchResponse search(String indexName,String[] type ,String[] queryFields,String queryString,int pageNum,int pageSize) {
        ESearchConfig eSearchConfig = new ESearchConfig(indexName,type);
        eSearchConfig.setSearchType(ESearchConfig.SearchType.QUERY_FIELD);
        eSearchConfig.setQuery(queryString);
        eSearchConfig.setQueryFields(queryFields);
        eSearchConfig.setPageNum(pageNum);
        eSearchConfig.setPageSize(pageSize);

        return search(eSearchConfig);
    }


    /**
     * 范围查询
     * @param indexName
     * @param type
     * @param rangeQueryName 查询的字段
     * @param from
     * @param to
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> searchRange(String indexName,String type ,String rangeQueryName,Object from,Object to,Class<T> clazz){
        SearchResponse searchResponse = searchRange(indexName, type, rangeQueryName, from, to);
        return SearchUtils.getListFromResponse(searchResponse, indexName, clazz, null, false);
    }
    public <T> List<T> searchRange(String indexName,String[] type ,String rangeQueryName,Object from,Object to,Class<T> clazz){
        SearchResponse searchResponse = searchRange(indexName, type, rangeQueryName, from, to);
        return SearchUtils.getListFromResponse(searchResponse, indexName, clazz, null, false);
    }
    public SearchResponse searchRange(String indexName,String type ,String rangeQueryName,Object from,Object to){
        String[] types = {type};
        return searchRange(indexName, types, rangeQueryName, from, to);
    }
    public SearchResponse searchRange(String indexName,String[] type ,String rangeQueryName,Object from,Object to){
        ESearchConfig eSearchConfig = new ESearchConfig(indexName,type);
        eSearchConfig.setSearchType(ESearchConfig.SearchType.RANGE);
        eSearchConfig.setRangeField(rangeQueryName);
        eSearchConfig.setRangeFrom(from);
        eSearchConfig.setRangeTo(to);
        return search(eSearchConfig);
    }





    public <T> List<T> searchRange(String indexName,String type ,Class<T> clazz,RangeBean... rangeBeans){
        String[] types = {type};
        return searchRange(indexName, types, clazz, rangeBeans);
    }

    public MultiSearchResponse searchRange(String indexName,String type ,RangeBean... rangeBeans){
        String[] types = {type};
        return searchRange(indexName, types, rangeBeans);
    }
    public <T> List<T> searchRange(String indexName,String[] types ,Class<T> clazz,RangeBean... rangeBeans){
        List<T> list = new ArrayList<>();
        MultiSearchResponse response = searchRange(indexName,types,rangeBeans);
        Iterator<MultiSearchResponse.Item> iter = response.iterator();
        while(iter.hasNext()){
            MultiSearchResponse.Item item = iter.next();
            SearchResponse searchResponse = item.getResponse();
            list.addAll(SearchUtils.getListFromResponse(searchResponse, indexName, clazz, null, false));
        }
        return list;
    }
    public MultiSearchResponse searchRange(String indexName,String[] types ,RangeBean... rangeBeans){
        MultiSearchRequestBuilder multiSearch = client.prepareMultiSearch();
        for(RangeBean rangeBean:rangeBeans){
            SearchRequestBuilder srb = client.prepareSearch(indexName)
                    .setTypes(types).setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                    .setExplain(true)
                    .setPostFilter(QueryBuilders.rangeQuery(rangeBean.getField())
                            .from(rangeBean.getFrom()).to(rangeBean.getTo())).setFrom(1).setSize(9999);
            multiSearch.add(srb);
        }
        MultiSearchResponse response = multiSearch.execute().actionGet();
        return response;
    }

    /**
     * 搜索
     * @param indexName
     * @param type
     * @param queryString 搜索的关键词
     * @param rangeQueryName 范围查询字段
     * @param from 开始
     * @param to 结束
     * @param pageNum 页码
     * @param pageSize 每页个数
     * @return
     */
    @Deprecated
    public SearchResponse search(String indexName,String[] type,String queryString ,String rangeQueryName,Object from,Object to,int pageNum,int pageSize){
        SearchRequestBuilder srb = client.prepareSearch(indexName)
                .setTypes(type).setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setExplain(true);

        if(!StringTools.isEmpty(queryString)){
            srb.setQuery(QueryBuilders.queryStringQuery(queryString));
//            srb.setQuery(QueryBuilders.multiMatchQuery(queryString,type))
        }
        if(!StringTools.isEmpty(rangeQueryName)){
            srb.setPostFilter(QueryBuilders.rangeQuery(rangeQueryName).from(from).to(to));
        }
        if(pageNum != 0 && pageSize != 0){
            int start = ArraysTools.getStart(pageNum,pageSize);
            srb.setFrom(start).setSize(pageSize);
        }
        SearchResponse searchResponse = srb.execute().actionGet();
        return searchResponse;
    }


    /**
     * 搜索
     * @param eSearchConfig
     * @return
     */
    public SearchResponse search(ESearchConfig eSearchConfig){
        String index = eSearchConfig.getIndex();
        String[] types = eSearchConfig.getTypes();

        SearchRequestBuilder srb = client.prepareSearch(index)
                .setTypes(types).setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setExplain(true);

        /**
         * 搜索全部字段
         */
        if(ESearchConfig.SearchType.QUERY_ALL.equals(eSearchConfig.getSearchType())){
            String query = eSearchConfig.getQuery();
            srb.setQuery(QueryBuilders.queryStringQuery(query));
        }
        if(ESearchConfig.SearchType.QUERY_FIELD.equals(eSearchConfig.getSearchType())){
            String query = eSearchConfig.getQuery();
            String[] queryFields = eSearchConfig.getQueryFields();
            int length = queryFields.length;
            String[] fields = new String[length+1];
            for(int i=0;i<length;i++){
                fields[i]=queryFields[i];
            }
            fields[length] = "pinyin";
//            queryFields[length] = "pinyin";
            srb.setQuery(QueryBuilders.multiMatchQuery(query,fields));
        }
        /**
         * 搜索范围
         */
        if(ESearchConfig.SearchType.RANGE.equals(eSearchConfig.getSearchType())){
            String rangeField = eSearchConfig.getRangeField();
            Object rangeFrom = eSearchConfig.getRangeFrom();
            Object rangeTo = eSearchConfig.getRangeTo();
            srb.setPostFilter(QueryBuilders.rangeQuery(rangeField).from(rangeFrom).to(rangeTo));
        }

        int pageNum = eSearchConfig.getPageNum();
        int pageSize = eSearchConfig.getPageSize();

        if(pageNum != 0 && pageSize != 0){
            int start = ArraysTools.getStart(pageNum,pageSize);
            srb.setFrom(start).setSize(pageSize);
        }else if(pageNum == 0 && pageSize == 0){
            srb.setFrom(1).setSize(9999);
        }
        SearchResponse searchResponse = srb.execute().actionGet();
        return searchResponse;
    }


    @Deprecated
    public SearchResponse search(String indexName,String type,String queryString ,String rangeQueryName,Object from,Object to,int pageNum,int pageSize){
        String[] types = {type};
        return search(indexName, types, queryString, rangeQueryName, from, to, pageNum, pageSize);
    }

}
